namespace FIREFLYX {
    /** 样例接口 */
    export interface IExample {
        Init(canvas: HTMLCanvasElement): any;
    }

    /** 纹理数据 */
    export class TextureData {
        public widith: number;
        public height: number;
        public glTextrue: WebGLTexture;
    }

    /** 相机观察环境 */
    export interface ICamViewEnv {
        camera: Camera;
        camHover: CameraHoverScript;
    }

    /**
     * 运行时 加载 .js 库
     * @param url .js 文件 URL
     */
    export function LoadJSLib(url: string): Promise<any> {
        return new Promise<any>((res, rej) => {
            let htmlS = document.createElement("script");
            htmlS.src = url;
            //挂载到doc
            let attParent = document.childNodes[document.childNodes.length - 1];
            attParent.appendChild(htmlS);
            htmlS.onload = () => {
                //code加载完毕
                res(null);
            };
            htmlS.onerror = (err) => {
                rej(`error : ${err}`);
            };
        });
    }

    /**
     * 初始shader
     * @param gl webgl上下文对象
     * @param vs 顶点程序code
     * @param fs 片源程序code
     * @returns webgl程序对象
     */
    // tslint:disable-next-line: only-arrow-functions
    export function InitShader(gl: WebGL2RenderingContext, vs: string, fs: string): WebGLProgram {
        //顶点shader
        //创建webglShader 对象
        let vsSh = gl.createShader(gl.VERTEX_SHADER);
        //将字符串程序代码传入
        gl.shaderSource(vsSh, vs);
        //编译字符串程序代码成二进制对象,便于webgl使用
        gl.compileShader(vsSh);
        ckShaderCompileS(gl, vsSh, "vs");

        //片源shader
        let fsSh = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fsSh, fs);
        gl.compileShader(fsSh);
        ckShaderCompileS(gl, fsSh, "fs");

        //创建WebGLProgram
        let pVs = gl.createProgram();
        //附加 shader程序
        gl.attachShader(pVs, vsSh);
        gl.attachShader(pVs, fsSh);
        //如果 vsSh ， fsSh 不需要缓存，可 gl.deleteShader(vsSh);

        //shader err log
        let log = gl.getShaderInfoLog(vsSh);
        if (log) {
            console.error(log);
        }

        log = gl.getShaderInfoLog(fsSh);
        if (log) {
            console.error(log);
        }

        //链接到一个已经 attach的程序
        gl.linkProgram(pVs);
        // //添加指定的程序 到当前的渲染状态中去 (为什么 要 link 又要 use ?)
        // gl.useProgram(pVs);
        let r3 = gl.getProgramParameter(pVs, gl.LINK_STATUS);
        if (r3 == false) {
            // console.error("vs:" + nameVS + "   fs:" + nameFS + "a webgl program error:" + webgl.getProgramInfoLog(program));
            console.error("a webgl program error:" + gl.getProgramInfoLog(pVs));
            // gl.deleteProgram(pVs);
            // return null;
        }

        GetErrorStr(gl);
        return pVs;

    }

    /**
     * 检查shader编译是否成功
     * @param gl webgl上下文对象
     * @param sh shader程序对象
     * @param info 错误时log 信息
     */
    // eslint-disable-next-line no-inner-declarations
    function ckShaderCompileS(gl: WebGL2RenderingContext, sh: WebGLShader, info = "") {
        let s = gl.getShaderParameter(sh, gl.COMPILE_STATUS);
        if (s == false) {
            console.error(`compiles shader error : ${info}`);
        }
    }

    /**
     * 获取webgl错误信息
     * @param gl webgl上下文对象
     */
    // tslint:disable-next-line: only-arrow-functions
    export function GetErrorStr(gl: WebGL2RenderingContext) {
        let str = "";
        let val = gl.getError();
        switch (val) {
            case gl.INVALID_ENUM: str = "An unacceptable value has been specified for an enumerated argument. "; break;
            case gl.INVALID_VALUE: str = "A numeric argument is out of range. "; break;
            case gl.INVALID_OPERATION: str = "The specified command is not allowed for the current state. "; break;
            case gl.INVALID_FRAMEBUFFER_OPERATION: str = "The currently bound framebuffer is not framebuffer complete when trying to render to or to read from it."; break;
            case gl.OUT_OF_MEMORY: str = "Not enough memory is left to execute the command."; break;
            case gl.CONTEXT_LOST_WEBGL: str = `If the WebGL context is lost, this error is returned on the first call to getError.
                                               Afterwards and until the context has been restored, it returns gl.NO_ERROR.`;
                // tslint:disable-next-line: align
                break;
            default:
        }

        if (str != "")
            console.error(`webgl error : ${str}`);
    }

    /**
     * 生成相机观察环境对象
     * @returns 
     */
    // tslint:disable-next-line: only-arrow-functions
    export function GenCamViewEnv(): ICamViewEnv {
        let result: ICamViewEnv;
        //相机
        let camGameObject = new GameObject("cam");
        let camera = camGameObject.addComponent<Camera>(Camera);
        camera.near = 0.1;
        camera.far = 1000;
        // camera.fov = 60 * Math.PI / 180;
        camera.reflashASP();
        // camGameObject.transform.position.z = -8;
        camGameObject.transform.position.z = 8;
        //camHover
        let camHover = camGameObject.addComponent<CameraHoverScript>(CameraHoverScript);
        camHover.start();
        // camHover.lookAtTarget = cube.transform;
        camHover.lookAtPoint = new Vector3();
        camHover.scaleSpeed = 0.5;
        camHover.distance = 5;
        camHover.tiltAngle = 20;
        camHover.panAngle = 180;

        //connet
        result = {
            camera,
            camHover
        };

        return result;
    }

    /**
     * 生成场景主灯光（方向光）
     * @param rX x轴旋转度
     * @param rY y轴旋转度
     * @param r 光色红
     * @param g 光色绿
     * @param b 光色蓝
     * @returns 灯光对象
     */
    export function GenMainLight(rX = 0, rY = 90, r = 1, g = 1, b = 0.8): Light {
        //点状方向光源
        const lightNode = new GameObject("lightNode");
        const toRadian = 0.0174;
        Quaternion.RotationYawPitchRollToRef(rY * toRadian, rX * toRadian, 0, lightNode.transform.localRotation);    //灯光角度调整
        lightNode.transform.localRotation = lightNode.transform.localRotation;
        const light = lightNode.addComponent<Light>(Light);
        light.color = new Color4(r, g, b, 1);                 //光源色
        light.type = LightType.DIRECTIONAL;                     //光源类型
        return light;
    }

    /**
     * 生成场景天空盒
     * @param skyboxCubeTex 天空盒 纹理
     */
    export function AttachSkyBox(skyboxCubeTex: TextureCube) {
        const renderCtx = SceneManager.renderContext;
        const skyboxMtr = renderCtx.skyboxMaterial = new Material(`skybox`);
        skyboxMtr.shader = DefaultAsset.GetShader(BUILTIN_SHADER_SKY_BOX);
        skyboxMtr.setProperty("u_skybox", skyboxCubeTex);
    }

    /**
     * 显示 性能小工具
     */
    export async function ShowPerformanceMonitor() {
        //加载 stat js 
        await LoadJSLib(`./lib/stats.min.js`);
        if (!globalThis.Stats) return;
        const stats = new globalThis.Stats();
        stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
        document.body.appendChild(stats.dom);

        //event
        GlobalEventManager.Instance.addListener("appLoopStart", () => {
            stats.begin();
        }, this);

        GlobalEventManager.Instance.addListener("appLoopEnd", () => {
            stats.end();
        }, this);
    }

    /**
     * 加载 cubeMap 通过 htmlImage 
     * @param cubeMapRes cubeMap资源名
     * @param fileSuffix 文件后缀
     * @returns 
     */
    export async function LoadCubeMapByHtmlImages<t extends keyof HtmlImageSuffix>(cubeMapRes: string, fileSuffix: t) {
        if (fileSuffix.indexOf(`.hi.`) == -1) {
            console.error(`file Suffix Must contain .hi `);
            return;
        }
        //加载 所有 纹理
        let basePath = `res/textures/cubes/${cubeMapRes}`;
        let cubeList = [`posx`, `negx`, `posy`, `negy`, `posz`, `negz`];
        let ps: Promise<HtmlImage>[] = [];
        for (let i = 0; i < 6; i++) {
            let face = cubeList[i];
            let url = `${basePath}/${face}${fileSuffix}`;
            ps.push(AssetManager.LoadFile(url) as Promise<HtmlImage>);
        }

        let hImgs = await Promise.all(ps);
        let result = new TextureCube(`cubeMap_${cubeMapRes}`);
        let rawHImgs = hImgs.map((hImg) => { return hImg.image; });
        result.setTextureAllData(rawHImgs);
        result.apply();
        return result;
    }
}